1include <gridfinity-rebuilt-utility.scad>
  2
  3// ===== INFORMATION ===== //
  4/*
  5 IMPORTANT: rendering will be better for analyzing the model if fast-csg is enabled. As of writing, this feature is only available in the development builds and not the official release of OpenSCAD, but it makes rendering only take a couple seconds, even for comically large bins. Enable it in Edit > Preferences > Features > fast-csg
  6
  7https://github.com/kennetek/gridfinity-rebuilt-openscad
  8
  9*/
 10
 11// ===== PARAMETERS ===== //
 12
 13/* [Special Variables] */
 14$fa = 8;
 15$fs = 0.25;
 16
 17/* [Bin or Base] */
 18type = 0; // [0:bin, 1:base]
 19
 20/* [Printer Settings] */
 21// extrusion width (walls will be twice this size)
 22nozzle = 0.6;
 23// slicer layer size
 24layer = 0.35;
 25// number of base layers on build plate
 26bottom_layer = 3;
 27
 28/* [General Settings] */
 29// number of bases along x-axis
 30gridx = 1;
 31// number of bases along y-axis
 32gridy = 1;
 33// bin height. See bin height information and "gridz_define" below.
 34gridz = 6;
 35// number of compartments along x-axis
 36n_divx = 2;
 37
 38/* [Toggles] */
 39// toggle holes on the base for magnet
 40enable_holes = true;
 41// round up the bin height to match the closest 7mm unit
 42enable_zsnap = false;
 43// toggle the lip on the top of the bin that allows stacking
 44enable_lip = true;
 45// chamfer inside bin for easy part removal
 46enable_scoop_chamfer = true;
 47// funnel-like features on the back of tabs for fingers to grab
 48enable_funnel = true;
 49// front inset (added for strength when there is a scoop)
 50enable_inset = true;
 51// "pinches" the top lip of the bin, for added strength
 52enable_pinch = true;
 53
 54/* [Styles] */
 55// determine what the variable "gridz" applies to based on your use case
 56gridz_define = 0; // [0:gridz is the height of bins in units of 7mm increments - Zack's method,1:gridz is the internal height in millimeters, 2:gridz is the overall external height of the bin in millimeters]
 57// how tabs are implemented
 58style_tab = 0; // [0:continuous, 1:broken, 2:auto, 3:right, 4:center, 5:left, 6:none]
 59// where to put X cutouts for attaching bases
 60// selecting none will also disable crosses on bases
 61style_base = 0; // [0:all, 1:corners, 2:edges, 3:auto, 4:none]
 62
 63// tab angle
 64a_tab = 40;
 65
 66// ===== IMPLEMENTATION ===== //
 67
 68color("tomato")
 69if (type != 0) gridfinityBaseVase(); // Generate a single base
 70else gridfinityVase(); // Generate the bin
 71
 72
 73// ===== CONSTRUCTION ===== //
 74
 75d_bottom = layer*(max(bottom_layer,1));
 76x_l = l_grid/2;
 77
 78dht = (gridz_define==0)?gridz*7 : (gridz_define==1)?h_bot+gridz+h_base : gridz-(enable_lip?3.8:0);
 79d_height = (enable_zsnap?((abs(dht)%7==0)?dht:dht+7-abs(dht)%7):dht)-h_base;
 80
 81d_fo1 = 2*r_fo1;
 82
 83f2c = sqrt(2)*(sqrt(2)-1); // fillet to chamfer ratio
 84me = ((gridx*l_grid-0.5)/n_divx)-nozzle*4-d_fo1-12.7-4;
 85m = min(d_tabw/1.8 + max(0,me), d_tabw/1.25);
 86d_ramp = f2c*(l_grid*((d_height-2)/7+1)/12-r_f2)+d_wall2;
 87d_edge = ((gridx*l_grid-0.5)/n_divx-d_tabw-d_fo1)/2;
 88n_st = gridz <= 3 ? 6 : d_edge < 2 && style_tab != 0 && style_tab != 6 ? 1 : style_tab == 1 && n_divx <= 1? 0 : style_tab;
 89
 90n_x = (n_st==0?1:n_divx);
 91spacing = (gridx*l_grid-0.5)/(n_divx);
 92shift = n_st==3?-1:n_st==5?1:0;
 93shiftauto = function (a,b) n_st!=2?0:a==1?-1:a==b?1:0;
 94
 95xAll = function (a,b) true;
 96xCorner = function(a,b) (a==1||a==gridx)&&(b==1||b==gridy);
 97xEdge = function(a,b) (a==1)||(a==gridx)||(b==1)||(b==gridy);
 98xAuto = function(a,b) xCorner(a,b) || (a%2==1 && b%2 == 1);
 99xNone = function(a,b) false;
100xFunc = [xAll, xCorner, xEdge, xAuto, xNone];
101
102
103module gridfinityVase() {
104    $dh = d_height;
105    difference() {
106        union() {
107            difference() {
108                block_vase_base();
109
110                if (n_st != 6)
111                transform_style()
112                transform_vtab_base((n_st<2?gridx*l_grid/n_x-0.5-d_fo1:d_tabw)-nozzle*4)
113                block_tab_base(-nozzle*sqrt(2));
114            }
115
116            if (enable_scoop_chamfer)
117            intersection() {
118                block_vase();
119                translate([0,gridy*l_grid/2-0.25-d_wall2/2,d_height/2+0.1])
120                cube([gridx*l_grid,d_wall2,d_height-0.2],center=true);
121            }
122
123            if (enable_funnel && gridz > 3)
124            pattern_linear((n_st==0?n_divx>1?n_divx:gridx:1), 1, (gridx*l_grid-d_fo1)/(n_st==0?n_divx>1?n_divx:gridx:1))
125            transform_funnel()
126            block_funnel_outside();
127
128            if (n_divx > 1)
129            pattern_linear(n_divx-1,1,(gridx*l_grid-0.5)/(n_divx))
130            block_divider();
131
132            if (n_divx < 1)
133            pattern_linear(n_st == 0 ? n_divx>1 ? n_divx-1 : gridx-1 : 1, 1, (gridx*l_grid-d_fo1)/((n_divx>1 ? n_divx : gridx)))
134            block_tabsupport();
135        }
136
137        if (enable_funnel && gridz > 3)
138        pattern_linear((n_st==0?n_divx>1?n_divx:gridx:1), 1, (gridx*l_grid-d_fo1)/(n_st==0?n_divx>1?n_divx:gridx:1))
139        transform_funnel()
140        block_funnel_inside();
141
142        if (!enable_lip)
143        translate([0,0,1.5*d_height])
144        cube([gridx*l_grid,gridy*l_grid,d_height], center=true);
145
146        block_x();
147        block_inset();
148        if (enable_pinch)
149        block_pinch();
150
151        if (bottom_layer <= 0)
152        translate([0,0,-50+layer+0.01])
153        cube([gridx*l_grid*10,gridy*l_grid*10,100], center=true);
154    }
155}
156
157module gridfinityBaseVase() {
158    difference() {
159    union() {
160    difference() {
161        intersection() {
162            block_base_blank(0);
163            translate([0,0,-h_base-1])
164            rounded_rectangle(l_grid-0.5-0.005, l_grid-0.5-0.005, h_base*10, r_fo1+0.001);
165        }
166        translate([0,0,0.01])
167        difference() {
168            block_base_blank(nozzle*4);
169            translate([0,0,-h_base])
170            cube([l_grid*2,l_grid*2,d_bottom*2],center=true);
171        }
172        // magic slice
173        rotate([0,0,90])
174        translate([0,0,-h_base+d_bottom+0.01])
175        cube([0.001,l_grid*gridx,d_height+d_bottom*2]);
176
177    }
178
179    pattern_circular(4)
180    intersection() {
181        rotate([0,0,45])
182        translate([-nozzle,3,-h_base+d_bottom+0.01])
183        cube([nozzle*2,l_grid*gridx,d_height+d_bottom*2]);
184
185        block_base_blank(nozzle*4-0.1);
186    }
187    if (enable_holes)
188    pattern_circular(4)
189    block_magnet_blank(nozzle);
190    }
191    if (enable_holes)
192    pattern_circular(4)
193    block_magnet_blank(0, false);
194
195    translate([0,0,h_base/2])
196    cube([l_grid*2, l_grid*2, h_base], center = true);
197    }
198
199    if (style_base != 4)
200    linear_extrude(d_bottom)
201    profile_x(0.1);
202}
203
204module block_magnet_blank(o = 0, half = true) {
205    translate([d_hole/2,d_hole/2,-h_base+0.1])
206    difference() {
207        hull() {
208            cylinder(r = r_hole2+o, h = h_hole*2, center = true);
209            cylinder(r = (r_hole2+o)-(h_base+0.1-h_hole), h = (h_base+0.1)*2, center = true);
210        }
211        if (half)
212        mirror([0,0,1])
213        cylinder(r=(r_hole2+o)*2, h = (h_base+0.1)*4);
214    }
215}
216
217module block_base_blank(o = 0) {
218    mirror([0,0,1]) {
219        hull() {
220            rounded_square(l_grid-o-0.05-2*r_c2-2*r_c1, h_base, r_fo3);
221            rounded_square(l_grid-o-0.05-2*r_c2, h_base-r_c1, r_fo2);
222        }
223        hull() {
224            rounded_square(l_grid-o-0.05-2*r_c2, r_c2, r_fo2);
225            mirror([0,0,1])
226            rounded_square(l_grid-o-0.05, d_bottom, r_fo1);
227        }
228    }
229}
230
231module block_pinch() {
232    sweep_rounded(gridx*l_grid-2*r_base-0.5-0.001, gridy*l_grid-2*r_base-0.5-0.001)
233    translate([r_base,0,0])
234    mirror([1,0,0])
235    translate([0,-(-d_height-h_base/2+r_c1),0])
236    copy_mirror([0,1,0])
237    difference() {
238        offset(delta = -nozzle*sqrt(2))
239        translate([0,-d_height-h_base/2+r_c1,0])
240        union() {
241            profile_wall_sub();
242            mirror([1,0,0])
243            square([10,d_height+h_base]);
244        }
245
246        translate([0,-50,0])
247        square([100,100], center = true);
248
249        translate([d_wall2-nozzle*2-d_clear*2,0,0])
250        square(r_c2*2);
251    }
252}
253
254module block_tabsupport() {
255    intersection() {
256        translate([0,0,0.1])
257        block_vase(d_height*4);
258
259        cube([nozzle*2, gridy*l_grid, d_height*3], center=true);
260
261        transform_vtab_base(gridx*l_grid*2)
262        block_tab_base(-nozzle*sqrt(2));
263    }
264}
265
266module block_divider() {
267    difference() {
268        intersection() {
269            translate([0,0,0.1])
270            block_vase();
271            cube([nozzle*2, gridy*l_grid, d_height*2], center=true);
272        }
273
274        if (n_st == 0) block_tab(0.1);
275        else block_divider_edgecut();
276
277        // cut divider clearance on negative Y side
278        translate([-gridx*l_grid/2,-(gridy*l_grid/2-0.25),0])
279        cube([gridx*l_grid,nozzle*2+0.1,d_height*2]);
280
281        // cut divider clearance on positive Y side
282        mirror([0,1,0])
283        if (enable_scoop_chamfer)
284            translate([-gridx*l_grid/2,-(gridy*l_grid/2-0.25),0])
285            cube([gridx*l_grid,d_wall2+0.1,d_height*2]);
286        else block_divider_edgecut();
287
288        // cut divider to have clearance with scoop
289        if (enable_scoop_chamfer)
290        transform_scoop()
291        offset(delta = 0.1)
292        polygon([
293            [0,0],
294            [d_ramp,d_ramp],
295            [d_ramp,d_ramp+nozzle/sqrt(2)],
296            [-nozzle/sqrt(2),0]
297        ]);
298    }
299
300    // divider slices
301    difference() {
302        for (i = [0:(d_height-d_bottom)/(layer)]) {
303
304        if (2*i*layer < d_height-layer/2-d_bottom-0.1)
305        mirror([0,1,0])
306        translate([0,(gridy*l_grid/2-0.25-nozzle)/2,layer/2+d_bottom+2*i*layer])
307        cube([nozzle*2-0.01,gridy*l_grid/2-0.25-nozzle,layer],center=true);
308
309        if ((2*i+1)*layer < d_height-layer/2-d_bottom-0.1)
310        translate([0,(gridy*l_grid/2-0.25-nozzle)/2,layer/2+d_bottom+(2*i+1)*layer])
311        cube([nozzle*2-0.01,gridy*l_grid/2-0.25-nozzle,layer],center=true);
312
313        }
314
315        // divider slices cut to tabs
316        if (n_st == 0)
317        transform_style()
318        transform_vtab_base((n_st<2?gridx*l_grid/n_x-0.5-d_fo1:d_tabw)-nozzle*4)
319        block_tab_base(-nozzle*sqrt(2));
320    }
321}
322
323module block_divider_edgecut() {
324    translate([-50,-gridy*l_grid/2+0.25,0])
325    rotate([90,0,90])
326    linear_extrude(100)
327    offset(delta = 0.1)
328    profile_wall_sub();
329}
330
331module transform_funnel() {
332    if (me > 6 && enable_funnel && gridz > 3 && n_st != 6)
333    transform_style()
334    render()
335    children();
336}
337
338module block_funnel_inside() {
339    intersection() {
340        block_tabscoop(m-nozzle*3*sqrt(2), 0.003, nozzle*2, 0.01);
341        block_tab(0.1);
342    }
343}
344
345module block_funnel_outside() {
346    intersection() {
347        difference() {
348            block_tabscoop(m, 0, 0, 0);
349            block_tabscoop(m-nozzle*4*sqrt(2), 0.003, nozzle*2, -1);
350        }
351        block_tab(-nozzle*sqrt(2)/2);
352    }
353}
354
355module block_vase_base() {
356    difference() {
357        // base
358        translate([0,0,-h_base]) {
359            translate([0,0,-0.1])
360            color("firebrick")
361            block_bottom(d_bottom, gridx, gridy, l_grid);
362            color("royalblue")
363            block_wall(gridx, gridy, l_grid) {
364                if (enable_lip) profile_wall();
365                else profile_wall2();
366            }
367        }
368
369        // magic slice
370        rotate([0,0,90])
371        mirror([0,1,0])
372        translate([0,0,d_bottom+0.001])
373        cube([0.001,l_grid*gridx,d_height+d_bottom*2]);
374    }
375
376    // scoop piece
377    if (enable_scoop_chamfer)
378    transform_scoop()
379    polygon([
380        [0,0],
381        [d_ramp,d_ramp],
382        [d_ramp,d_ramp+0.6/sqrt(2)],
383        [-0.6/sqrt(2),0]
384    ]);
385
386    // outside tab cutter
387    if (n_st != 6)
388    translate([-(n_x-1)*spacing/2,0,0])
389    for (i = [1:n_x])
390    translate([(i-1)*spacing,0,0])
391    translate([shiftauto(i,n_x)*d_edge + shift*d_edge,0,0])
392    intersection() {
393        block_vase();
394        transform_vtab_base(n_st<2?gridx*l_grid/n_x-0.5-d_fo1:d_tabw)
395        profile_tab();
396    }
397}
398
399module profile_wall_sub_sub() {
400    polygon([
401        [0,0],
402        [nozzle*2,0],
403        [nozzle*2,d_height-1.2-d_wall2+nozzle*2],
404        [d_wall2-d_clear,d_height-1.2],
405        [d_wall2-d_clear,d_height+h_base],
406        [0,d_height+h_base]
407    ]);
408}
409
410module block_inset() {
411    ixx = (gridx*l_grid-0.5)/2;
412    iyy = d_height/1.875;
413    izz = sqrt(ixx^2+iyy^2)*tan(40);
414    if (enable_scoop_chamfer && enable_inset)
415    difference() {
416        intersection() {
417            rotate([0,90,0])
418            translate([-iyy,0,0])
419            block_inset_sub(iyy, gridx*l_grid, 45);
420
421            rotate([0,90,0])
422            translate([-iyy,0,0])
423            rotate([0,90,0])
424            block_inset_sub(ixx, d_height*2, 45);
425        }
426
427        mirror([0,1,0])
428        translate([-gridx*l_grid/2,-(gridy*l_grid-0.5)/2+d_wall2-2*nozzle,0])
429        cube([gridx*l_grid,izz,d_height*2]);
430    }
431}
432
433module block_inset_sub(x, y, ang) {
434    translate([0,(gridy*l_grid-0.5)/2+r_fo1,0])
435    mirror([0,1,0])
436    linear_extrude(y,center=true)
437    polygon([[-x,0],[x,0],[0,x*tan(ang)]]);
438}
439
440module transform_style() {
441    translate([-(n_x-1)*spacing/2,0,0])
442    for (i = [1:n_x])
443    translate([(i-1)*spacing,0,0])
444    translate([shiftauto(i,n_x)*d_edge + shift*d_edge,0,0])
445    children();
446}
447
448module block_flushscoop() {
449    translate([0,gridy*l_grid/2-d_wall2-nozzle/2-1,d_height/2])
450    linear_extrude(d_height)
451    union() {
452        copy_mirror([1,0,0])
453        polygon([[0,0],[gridx*l_grid/2-r_fo1,0],[gridx*l_grid/2-r_fo1,1],[gridx*l_grid/2-r_fo1-r_c1*5,d_wall2-nozzle*2+1],[0,d_wall2-nozzle*2+1]]);
454    }
455
456    transform_scoop()
457    polygon([[0,0],[d_ramp,0],[d_ramp,d_ramp]]);
458}
459
460module profile_tab() {
461    union() {
462        copy_mirror([0,1,0])
463        polygon([[0,0],[d_tabh*cos(a_tab),0],[d_tabh*cos(a_tab),d_tabh*sin(a_tab)]]);
464    }
465}
466
467module profile_tabscoop(m) {
468    polyhedron([[m/2,0,0],[0,-m,0],[-m/2,0,0],[0,0,m]], [[0,2,1],[1,2,3],[0,1,3],[0,3,2]]);
469}
470
471module block_tabscoop(a=m, b=0, c=0, d=-1) {
472    translate([0,d_tabh*cos(a_tab)-l_grid*gridy/2+0.25+b,0])
473    difference() {
474        translate([0,0,-d_tabh*sin(a_tab)*2+d_height+2.1])
475        profile_tabscoop(a);
476
477        translate([-gridx*l_grid/2,-m,-m])
478        cube([gridx*l_grid,m-d_tabh*cos(a_tab)+0.005+c,d_height*20]);
479
480        if (d >= 0)
481        translate([0,0,-d_tabh*sin(a_tab)+d_height+m/2+d+2.1])
482        cube([gridx*l_grid,gridy*l_grid,m],center=true);
483    }
484}
485
486module transform_vtab(a=0,b=1) {
487    transform_vtab_base(gridx*l_grid/b-0.5-d_fo1+a)
488    children();
489}
490
491module transform_vtab_base(a) {
492    translate([0,d_tabh*cos(a_tab)-l_grid*gridy/2+0.25,-d_tabh*sin(a_tab)+d_height+2.1])
493    rotate([90,0,270])
494    linear_extrude(a, center=true)
495    children();
496}
497
498module block_tab(del, b=1) {
499    transform_vtab(-nozzle*4, b)
500    block_tab_base(del);
501}
502
503module block_tab_base(del) {
504    offset(delta = del)
505    union() {
506        profile_tab();
507        translate([d_tabh*cos(a_tab),-d_tabh*sin(a_tab),0])
508        square([l_grid,d_tabh*sin(a_tab)*2]);
509    }
510}
511
512module transform_scoop() {
513    intersection() {
514        block_vase();
515        translate([0,gridy*l_grid/2-d_ramp,layer*max(bottom_layer*1)])
516        rotate([90,0,90])
517        linear_extrude(2*l_grid*gridx,center=true)
518        children();
519    }
520}
521
522module block_vase(h = d_height*2) {
523    translate([0,0,-0.1])
524    rounded_rectangle(gridx*l_grid-0.5-nozzle, gridy*l_grid-0.5-nozzle, h, r_base+0.01-nozzle/2);
525}
526
527module profile_x(x_f = 3) {
528    difference() {
529        square([x_l,x_l],center=true);
530
531        pattern_circular(4)
532        translate([0,nozzle*sqrt(2),0])
533        rotate([0,0,45])
534        translate([x_f,x_f,0])
535        minkowski() {
536            square([x_l,x_l]);
537            circle(x_f);
538        }
539    }
540}
541
542module block_x() {
543    translate([-(gridx-1)*l_grid/2,-(gridy-1)*l_grid/2,0])
544    for (i = [1:gridx])
545    for (j = [1:gridy])
546    if (xFunc[style_base](i,j))
547    translate([(i-1)*l_grid,(j-1)*l_grid,0])
548    block_x_sub();
549}
550
551module block_x_sub() {
552    linear_extrude(d_bottom*2+0.01,center=true)
553    offset(0.05)
554    profile_x();
555}